package http

import (
	"fmt"
	"net/http"
	"sync"
	"sync/atomic"

	"engine/log"

	"gitee.com/ameise84/e3dds"
	"gitee.com/ameise84/e3pool/go_pool"
	"gitee.com/ameise84/e3pool/obj_pool"
	"gitee.com/ameise84/e3utils/str_conv"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
)

var (
	_instID      atomic.Uint64
	_gRawCtxPool obj_pool.Pool[*rawContext]
)

func init() {
	_gRawCtxPool = obj_pool.NewPool[*rawContext](func() *rawContext {
		return &rawContext{
			id: str_conv.ToString(_instID.Add(1)),
		}
	})
}

func takeRawContext() *rawContext {
	return _gRawCtxPool.Get()
}

func freeRawContext(rc *rawContext) {
	go_pool.NewGoWorkerDo(nil, "free http raw Context", func(...any) {
		rc.wg.Wait() //等待消息返回
		rc.c = nil
		rc.actor = nil
		_gRawCtxPool.Put(rc)
	})
}

// 代理连接
type rawContext struct {
	id          string
	c           *gin.Context
	actor       e3dds.NetBehavior
	isConnected atomic.Bool
	wg          sync.WaitGroup

	recvCount uint64
}

func (ts *rawContext) E3LogMarshall() string {
	return fmt.Sprintf("ws raw Context[%s]", ts.id)
}

func (ts *rawContext) OnResponse(rsp e3dds.Response) {
	ts.wg.Done()
	ts.response(rsp.GetProtocName(), rsp.GetBody())
}

func (ts *rawContext) onRecv(protoc string) {
	ts.recvCount++
	msg := e3dds.TakeMessage()
	msg.SetID(ts.recvCount)
	msg.SetToActor(ts.actor.GetSource().ServiceID(), "", ts.actor.GetSource().ClerkID())
	msg.SetMsg(protoc, nil)
	msg.SetRspHandler(ts)
	msg.BindContext(&Context{ctx: ts.c})
	err := ts.actor.OnRecvNetMsg(msg)
	if err != nil {
		ts.c.AbortWithStatus(http.StatusForbidden)
		e3dds.FreeMessage(msg)
	} else {
		ts.wg.Add(1)
	}
}

func (ts *rawContext) Close() {
	if ts.isConnected.CompareAndSwap(true, false) {
		ts.c.Header("Connection", "close")
		ts.c.String(http.StatusServiceUnavailable, "this response will close the connection")
	}
}

func (ts *rawContext) onInit(c *gin.Context, actor e3dds.NetBehavior) {
	ts.c = c
	ts.actor = actor
	ts.isConnected.Store(true)
}

func (ts *rawContext) onClosed() {
	ts.isConnected.Store(false)
}

func (ts *rawContext) response(protoc string, body []byte) {
	c := ts.c
	if ts.isConnected.Load() {
		switch protoc {
		case ProtocString:
			c.String(http.StatusOK, str_conv.ToString(body))
		case ProtocJson:
			c.JSON(http.StatusOK, body)
		case ProtocProtobuf:
			c.ProtoBuf(http.StatusOK, body)
		case ProtocToml:
			c.TOML(http.StatusOK, body)
		case ProtocYaml:
			c.YAML(http.StatusOK, body)
		case ProtocXML:
			c.XML(http.StatusOK, body)
		default:
			err := errors.New("response protoc[" + protoc + "] not support")
			log.GetLogger().Error("http response").Err(err).Object(ts).Println()
		}
	}
}
